#include "xen/renderer/renderer2d.hpp"

namespace Xen {

struct Renderer2DStorage {
    Ref<VertexArray> vertex_array;
    Ref<Shader> shader;
    Ref<Texture2D> white_texture;
};

static Renderer2DStorage data;

void Renderer2D::Init() {
    data.vertex_array = VertexArray::Create();

    float rect_vertices[] = {
         0.5f,  0.5f, 0, 0, // right top
        -0.5f,  0.5f, 0, 0, // left top
        -0.5f, -0.5f, 0, 0, // left bottom
         0.5f, -0.5f, 0, 0  // right bottom
    };

    uint32_t indices[] = {
        0, 1, 2,
        0, 2, 3
    };

    auto vertex_buffer = VertexBuffer::Create(rect_vertices, sizeof(rect_vertices));

    BufferLayout vertex_layout = {
        {"a_Position", ShaderDataType::Float2},
        {"a_TexCoord", ShaderDataType::Float2},
    };
    vertex_buffer->SetLayout(vertex_layout);

    auto index_buffer = IndexBuffer::Create(indices, sizeof(indices)/sizeof(indices[0]));

    data.vertex_array->SetVertexBuffer(vertex_buffer);
    data.vertex_array->SetIndexBuffer(index_buffer);

    data.shader = Shader::CreateFromFile("Shader2D", PROJECT_PATH "/sandbox/assets/shaders/Texture2D.glsl");

    data.white_texture = Texture2D::CreateColorTexture(1, 1);
    uint32_t white_color = 0xFFFFFFFF;
    data.white_texture->SetData(&white_color);
}

void Renderer2D::Shutdown() {
}

void Renderer2D::BeginScene(const OrthoCamera& camera) {
    Renderer::BeginScene(camera);
}

void Renderer2D::EndScene() {
    Renderer::EndScene();
}

void Renderer2D::DrawRect(const glm::vec2 &center, const glm::vec2 &size, const glm::vec4 &color, float z) {
    glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(center, 0.0))*
                      glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0));
    data.shader->Bind();
    data.shader->UniformFloat4("color", color);
    data.shader->UniformFloat("z_value", z);
    data.white_texture->Bind(0);
    Renderer::Submit(data.vertex_array, data.shader, model);
    data.white_texture->Unbind();
}

void Renderer2D::DrawTexture(const glm::vec2& center, const glm::vec2& size, const Ref<Texture2D>& texture, float z) {
    glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(center, 0.0))*
                      glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0));

    float coords[2];
    coords[0] = 1; coords[1] = 1;
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 0) * sizeof(float), 2 * sizeof(float), coords);
    coords[0] = 0; coords[1] = 1;
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 1) * sizeof(float), 2 * sizeof(float), coords);
    coords[0] = 0; coords[1] = 0;
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 2) * sizeof(float), 2 * sizeof(float), coords);
    coords[0] = 1; coords[1] = 0;
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 3) * sizeof(float), 2 * sizeof(float), coords);

    data.shader->Bind();
    data.shader->UniformInt("tex", 0);
    data.shader->UniformFloat4("color", {1.0f, 1.0f, 1.0f, 1.0f});
    data.shader->UniformFloat("z_value", z);
    texture->Bind(0);
    Renderer::Submit(data.vertex_array, data.shader, model);
    texture->Unbind();
}

void Renderer2D::DrawSubTexture(const glm::vec2 &center, const glm::vec2 &size, const Ref<SubTexture2D> &texture, float z) {
    glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(center, 0.0))*
                      glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0));

    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 0) * sizeof(float), 2 * sizeof(float), glm::value_ptr(texture->GetTexCoords().at(0)));
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 1) * sizeof(float), 2 * sizeof(float), glm::value_ptr(texture->GetTexCoords().at(1)));
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 2) * sizeof(float), 2 * sizeof(float), glm::value_ptr(texture->GetTexCoords().at(2)));
    data.vertex_array->GetVertexBuffer()->SetData((2 + 4 * 3) * sizeof(float), 2 * sizeof(float), glm::value_ptr(texture->GetTexCoords().at(3)));

    data.shader->Bind();
    data.shader->UniformInt("tex", 0);
    data.shader->UniformFloat4("color", {1.0f, 1.0f, 1.0f, 1.0f});
    data.shader->UniformFloat("z_value", z);
    texture->GetTexture()->Bind(0);
    Renderer::Submit(data.vertex_array, data.shader, model);
}

}
